package de.softwareforge.published.doclet;

/*
 * Copyright (C) 2007-2008 Henning P. Schmiedehausen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.sun.javadoc.DocErrorReporter;
import com.sun.javadoc.LanguageVersion;
import com.sun.javadoc.RootDoc;
import com.sun.tools.doclets.standard.Standard;

import de.softwareforge.published.proxy.ProxyFactory;
import de.softwareforge.published.proxy.PublishedDocletProxyController;
import de.softwareforge.published.util.Locale;
import de.softwareforge.published.util.PublishedStatus;
import de.softwareforge.published.util.StatusCalculator;

import static de.softwareforge.published.util.LocaleMsg.ERROR_VISIBILITY_CHANGE;
import static de.softwareforge.published.util.LocaleMsg.NOTICE_VISIBILITY_CHANGE;


/**
 * A doclet which renders Javadocs according to a given publishing level.
 *
 * <p>The following configuration options are understood.</p>
 *
 * <pre>
 *   -hideuntagged        - When given, display only tagged elements.
 *   -publishlevel        - Minimum published API level to include in the documentation. Default is to include everything.
 *   -defaultpublishlevel - When given, all elements that have no publish level will get this level assigned. Default is not to assign any level.
 *   -novisibilitychanges - When given, the javadoc rendering stops when an invisible elements needs to become visible.
 *   -ignoretags          - Render the Javadocs like the standard doclet, but ignore all 'api.level' tags which show up as errors with the
 *                          standard doclet.
 *   -publishall          - Render all api.level tags. Ignores -hideuntagged and -publishlevel.
 *   -rootpublishlevel    - Override the default publish level for the root document.
 *
 * </pre>
 *
 * <p>Theory of operation: Each element in a Javadoc (C'tor, Field, Method, Overview, Package, Type) can get an additional tag to
 * mark its 'publishing state'. The state is calculated like this:</p>
 *
 * <p>- If an element has an explicit state set, this state is reported. The state propagates further 'down' from the element until
 * another explicit element is met. - If any parent of an element has an explicit state, it gets calculated from there. If no status
 * is calculated from the element to the document root, the status is kept at 'unknown' and the -showunpublished option decided
 * whether it gets displayed or not.</p>
 *
 * <p>This doclet contains code based on the ExcludeDoclet written by Chris Nokleberg and published at <a
 * href="http://www.sixlegs.com/blog/java/exclude-javadoc-tag.html">http://www.sixlegs.com/blog/java/exclude-javadoc-tag.html</a>.
 * He put this code into public domain. Thanks, Chris!</p>
 *
 * @author <a href="mailto:henning@schmiedehausen.org">Henning P. Schmiedehausen</a>
 * @version $Id: PublishedDoclet.java 102 2008-02-24 22:14:11Z hps@intermeta.de $
 */
public class PublishedDoclet
{

    /**
     * The resource name for the locale messages.
     *
     * @api.level unpublished
     */
    public static final String LOCALE_RESOURCES = PublishedDoclet.class.getPackage().getName() + ".messages";

    private static final PublishedDoclet publishedDoclet = new PublishedDoclet();

    private Locale messages = null;

    private StatusCalculator statusCalculator = null;

    private PublishedDocletConfiguration conf = new PublishedDocletConfiguration();

    /**
     * @see Standard#languageVersion()
     */
    public static LanguageVersion languageVersion()
    {
        return publishedDoclet.doLanguageVersion();
    }

    /**
     * @see Standard#optionLength(String)
     */
    public static int optionLength(final String option)
    {
        return publishedDoclet.getConfiguration().optionLength(option);
    }

    /**
     * @see Standard#validOptions(String[][], DocErrorReporter)
     */
    public static boolean validOptions(final String[][] options, final DocErrorReporter reporter)
    {
        return publishedDoclet.getConfiguration().validOptions(options, reporter);
    }

    /**
     * @see Standard#start(RootDoc)
     */
    public static boolean start(final RootDoc root)
    {
        return publishedDoclet.doStart(root);
    }

    /**
     * Returns the current language version.
     *
     * @return A {@link LanguageVersion} object.
     */
    protected LanguageVersion doLanguageVersion()
    {
        return Standard.languageVersion();
    }

    /**
     * Run the doclet to process the documentation.
     *
     * @param root A root document representing the javadocs.
     *
     * @return True if the rendering was successful.
     */
    protected boolean doStart(final RootDoc root)
    {
        messages = new Locale(LOCALE_RESOURCES, root);
        statusCalculator = new StatusCalculator(this);

        PublishedDocletProxyController publishedDocletProxyController = new PublishedDocletProxyController(this);
        ProxyFactory proxyFactory = new ProxyFactory(publishedDocletProxyController);
        RootDoc rootDoc = (RootDoc) proxyFactory.wrapProxy(root, RootDoc.class);
        PublishedStatus rootStatus = new PublishedStatus(this);

        statusCalculator.buildStatusTree(rootStatus, rootDoc);

        if (!conf.isIgnoreTags())
        {
            do
            {
                statusCalculator.setNeedRerun(false);
                statusCalculator.buildVisibilityTree(rootDoc);

                if (statusCalculator.isNeedRerun())
                {
                    if (conf.isNoVisibilityChanges())
                    {
                        messages.error(ERROR_VISIBILITY_CHANGE);
                        return false;
                    }
                    else
                    {
                        messages.notice(NOTICE_VISIBILITY_CHANGE);
                    }
                }
            }
            while (statusCalculator.isNeedRerun());
        }

        publishedDocletProxyController.setExcludeMode(true);
        rootStatus.setIsReferenced(true); // Root Document is always visible!

        return Standard.start(rootDoc);
    }

    /**
     * Return a {@link Locale} object to send out messages, warnings and errors.
     *
     * @return A Locale object.
     *
     * @api.level unpublished
     */
    public Locale getMessages()
    {
        return messages;
    }

    /**
     * Returns the current doclet settings.
     *
     * @return The doclet settings.
     *
     * @api.level unpublished
     */
    public PublishedDocletConfiguration getConfiguration()
    {
        return this.conf;
    }

    /**
     * Returns the current doclet settings.
     *
     * @return The doclet settings.
     *
     * @api.level unpublished
     */
    public StatusCalculator getStatusCalculator()
    {
        return this.statusCalculator;
    }
}
